/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package deatax.game.model.importer;

import deatax.game.model.ModelAnim;
import deatax.game.model.ModelBone;
import deatax.game.model.ModelChannel;
import deatax.game.model.ModelMaterial;
import deatax.game.model.ModelMesh;
import deatax.game.model.ModelNode;
import deatax.game.model.ModelSegment;
import deatax.game.model.QuaternionKey;
import deatax.game.model.Vector3Key;
import deatax.math.Matrix4;
import deatax.math.Quaternion;
import deatax.math.Vector3;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 *
 * @author pson
 */
public class PMMLoader {

    private static ByteBuffer toBuffer(byte[] buffer) {
        ByteBuffer b = ByteBuffer.wrap(buffer);
        b.order(ByteOrder.LITTLE_ENDIAN);
        return b;
    }
    public static ModelNode loadModel(InputStream in) {

        ModelNode node = new ModelNode();

        //Start with the node
        byte[] buffer = new byte[256];
        
        ByteBuffer bb;
        try {
            //Read file version
            int n = in.read(buffer, 0, 2);
            if (n == 2) {
                byte major = buffer[0];
                byte minor = buffer[1];
                System.out.println("Model Version: " + major + "." + minor);
            } else {
                throw new Exception("File Error");
            }

            //Read Textures
            n = in.read(buffer, 0, 4);
            bb = toBuffer(buffer);
            int n_texture = bb.getInt();
            node.textures = new String[n_texture];
            for (int i = 0; i < n_texture; ++i) {
                n = in.read(buffer, 0, 4);
                bb = toBuffer(buffer);
                int n_length = bb.getInt();
                if (n_length > 0) {
                    n = in.read(buffer, 0, n_length);
                    String s = new String(buffer, 0, n);
                    node.textures[i] = s;
                } else {
                    node.textures[i] = null;
                }
            }

            //Read Materials
            n = in.read(buffer, 0, 4);
            bb = toBuffer(buffer);
            int n_mats = bb.getInt();
            node.materials = new ModelMaterial[n_mats];
            for (int i = 0; i < n_mats; ++i) {
                ModelMaterial mm = new ModelMaterial();
                n = in.read(buffer, 0, 72);
                if (n != 72) {
                    throw new Exception("File error");
                }
                bb = toBuffer(buffer);
                mm.diffuse = new float[4];
                mm.diffuse[0] = bb.getFloat();
                mm.diffuse[1] = bb.getFloat();
                mm.diffuse[2] = bb.getFloat();
                mm.diffuse[3] = bb.getFloat();

                mm.specular = new float[4];
                mm.specular[0] = bb.getFloat();
                mm.specular[1] = bb.getFloat();
                mm.specular[2] = bb.getFloat();
                mm.specular[3] = bb.getFloat();

                mm.ambient = new float[4];
                mm.ambient[0] = bb.getFloat();
                mm.ambient[1] = bb.getFloat();
                mm.ambient[2] = bb.getFloat();
                mm.ambient[3] = bb.getFloat();

                mm.emissive = new float[4];
                mm.emissive[0] = bb.getFloat();
                mm.emissive[1] = bb.getFloat();
                mm.emissive[2] = bb.getFloat();
                mm.emissive[3] = bb.getFloat();

                mm.shininess = bb.getFloat();

                mm.texture = bb.getInt();

                node.materials[i] = mm;
            }

            //Read Meshes
            n = in.read(buffer, 0, 4);
            bb = toBuffer(buffer);
            int n_mesh = bb.getInt();
            node.meshes = new ModelMesh[n_mesh];
            for (int i = 0; i < n_mesh; ++i) {

                ModelMesh mm = new ModelMesh();

                //Vertices
                n = in.read(buffer, 0, 8);
                bb = toBuffer(buffer);
                mm.mat = bb.getInt();
                //multiply by 8 for xyznnnuv format
                int n_vert = bb.getInt() * 8;

                mm.verts = new float[n_vert];
                
                byte[] bbuffer = new byte[2048];
                int n_remain = n_vert * 4;
                int c_vert = 0;
                //This needs to be while'd since verts can be very high
                //with a buffer size of 256, 64 floats are processed at a time
                while (n_remain > 0) {
                    n = in.read(bbuffer, 0, Math.min(n_remain, 2048));
                    bb = toBuffer(bbuffer);
                    for (int j = 0; j < n / 4; ++j) {
                        mm.verts[c_vert] = bb.getFloat();
                        c_vert++;
                    }
                    n_remain -= n;
                }

                //Faces
                n = in.read(buffer, 0, 4);
                bb = toBuffer(buffer);
                //Multiply by 3 for triangles
                int n_face = bb.getInt() * 3;

                mm.faces = new int[n_face];

                n_remain = n_face * 4;
                int c_face = 0;
                //Also while'd
                while (n_remain > 0) {
                    n = in.read(bbuffer, 0, Math.min(n_remain, 2048));
                    bb = toBuffer(bbuffer);
                    for (int j = 0; j < n / 4; ++j) {
                        mm.faces[c_face] = bb.getInt();
                        c_face++;
                    }
                    n_remain -= n;
                }

                //Bones
                n = in.read(buffer, 0, 4);
                bb = toBuffer(buffer);
                int n_bone = bb.getInt();

                mm.bones = new ModelBone[n_bone];
                for (int j = 0; j < n_bone; ++j) {
                    ModelBone mb = new ModelBone();
                    float[] off = new float[16];
                    //64 for 4x4 matrix, 4 for vert num
                    n = in.read(buffer, 0, 68);
                    bb = toBuffer(buffer);
                    for (int k = 0; k < 16; ++k) {
                        off[k] = bb.getFloat();
                    }
                    mb.offset = new Matrix4(off);

                    int n_bvert = bb.getInt();
                    mb.verts = new int[n_bvert];
                    mb.weights = new float[n_bvert];

                    int mb_remain = n_bvert * 8;
                    int c_bvert = 0;
                    while (mb_remain > 0) {
                        //Multiply by 4 for int/float, by 2 for 2 sets = 8
                        n = in.read(bbuffer, 0, Math.min(mb_remain, 2048));
                        bb = toBuffer(bbuffer);

                        for (int k = 0; k < n_bvert; ++k) {
                            mb.verts[c_bvert] = bb.getInt();
                            mb.weights[c_bvert] = bb.getFloat();
                            c_bvert++;
                        }
                        mb_remain -= n;
                    }
                    
                    mm.bones[j] = mb;
                }
                node.meshes[i] = mm;
            }

            //Animations
            n = in.read(buffer, 0, 4);
            bb = toBuffer(buffer);
            int n_anim = bb.getInt();
            node.anims = new ModelAnim[n_anim];
            for (int i = 0; i < n_anim; ++i) {
                ModelAnim ma = new ModelAnim();
                //duration, ticks, num channel
                n = in.read(buffer, 0, 12);
                bb = toBuffer(buffer);
                ma.duration = bb.getInt();
                ma.tickps = bb.getInt();
                int n_chan = bb.getInt();
                ma.channels = new ModelChannel[n_chan];
                for (int j = 0; j < n_chan; ++j) {
                    ModelChannel mc = new ModelChannel();
                    //pre, post, num pos
                    n = in.read(buffer, 0, 12);
                    bb = toBuffer(buffer);
                    mc.pre = bb.getInt();
                    mc.post = bb.getInt();

                    //Position keys
                    int n_pos = bb.getInt();
                    mc.pos = new Vector3Key[n_pos];
                    for (int k = 0; k < n_pos; ++k) {
                        Vector3Key vk = new Vector3Key();
                        n = in.read(buffer, 0, 16);
                        bb = toBuffer(buffer);
                        vk.time = bb.getFloat();
                        vk.val = new Vector3(bb.getFloat(), bb.getFloat(), bb.getFloat());
                        mc.pos[k] = vk;
                    }

                    //Scale Keys
                    int n_scale = bb.getInt();
                    mc.scale = new Vector3Key[n_scale];
                    for (int k = 0; k < n_scale; ++k) {
                        Vector3Key vk = new Vector3Key();
                        n = in.read(buffer, 0, 16);
                        bb = toBuffer(buffer);
                        vk.time = bb.getFloat();
                        vk.val = new Vector3(bb.getFloat(), bb.getFloat(), bb.getFloat());
                        mc.scale[k] = vk;
                    }


                    //Rotate Keys
                    int n_rotate = bb.getInt();
                    mc.rotate = new QuaternionKey[n_rotate];
                    for (int k = 0; k < n_rotate; ++k) {
                        QuaternionKey qk = new QuaternionKey();
                        n = in.read(buffer, 0, 20);
                        bb = toBuffer(buffer);
                        qk.time = bb.getFloat();
                        qk.val = new Quaternion(bb.getFloat(), bb.getFloat(), bb.getFloat(), bb.getFloat());
                        mc.rotate[k] = qk;
                    }

                    ma.channels[j] = mc;
                }
                node.anims[i] = ma;
            }

            //Segments, recursive
            node.root = loadSegment(in);

            return node;

        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        return null;
    }

    private static ModelSegment loadSegment(InputStream in) throws IOException {

        ModelSegment ms = new ModelSegment();
        //64 for matrix 4x4, 4 for num mesh
        byte[] buffer = new byte[256];
        int n = in.read(buffer, 0, 68);
        ByteBuffer bb = toBuffer(buffer);
        float[] transform = new float[16];
        for (int i = 0; i < 16; ++i) {
            transform[i] = bb.getFloat();
        }
        int n_mesh = bb.getInt();
        ms.mesh = new int[n_mesh];
        
        n = in.read(buffer, 0, n_mesh * 4);
        bb = toBuffer(buffer);
        
        for (int i = 0; i < n_mesh; ++i) {
            ms.mesh[i] = bb.getInt();
        }
        
        n = in.read(buffer, 0, 4);
        bb = toBuffer(buffer);
        
        int n_child = bb.getInt();
        ms.children = new ModelSegment[n_child];
        for (int i = 0; i < n_child; ++i) {
            ms.children[i] = loadSegment(in);
        }
        
        return ms;
    }

}
